Make seatbelt.profileOverride dev-only (rejected by shipped builds) - #727
Open
Gudge (MGudgin) wants to merge 2 commits into
Open
Conversation
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
This was referenced Aug 1, 2026
Branden Bonaby (bbonaby)
previously approved these changes
Aug 3, 2026
Gudge (MGudgin)
dismissed
Branden Bonaby (bbonaby)’s stale review
August 4, 2026 00:55
The merge-base changed after approval.
Collaborator
|
Gudge (@MGudgin), just have a merge conflict with this one. |
This PR removes the catastrophic seatbelt.profileOverride escape hatch from release binaries. profileOverride replaces the entire generated deny-default Seatbelt profile with a caller-supplied string, so it must not be honorable in a shipped build. Release builds reject a config that sets it, with defense in depth at both the parse and build layers. Details * config_parser make_seatbelt_config returns a config error in release builds when profile_override is set, and logs a SECURITY line naming the field. Rejecting rather than dropping the field means a caller never runs under a policy they did not request: the generated profile can be materially more permissive than the custom one they supplied. This matches how the reserved learning-mode capabilities are handled. * profile_builder build_profile compiles the override branch out entirely under #[cfg(not(debug_assertions))], so a release binary builds the generated deny-default profile even if it were reached with an override set. The parser rejection alone would be a single point of failure. * Dev and debug builds honor the override for advanced testing. * Docs (seatbelt-backend.md, schema.md) and the wire-model doc comment describe the field as rejected by release builds. schema.md records that the released stable schemas describe it as generally usable, since those files are immutable and cannot be corrected in place. Tests * Release: seatbelt_profile_override_rejected_in_release asserts the parse fails, that the error names the field and states the dev-only rejection, and that a SECURITY line is logged. profile_override_branch_is_absent_in_release asserts the builder returns the generated deny-default profile rather than the caller's string, and that the request policy is reflected in it. * Debug: profile_override_passed_through_in_debug and profile_override_takes_precedence assert the dev behavior. * cargo test -p wxc_common -p seatbelt_common passes in both profiles: 555 + 36 in debug, 555 + 36 in release, 0 failed. * cargo fmt --all -- --check and cargo clippy --workspace --all-targets -D warnings both clean; codegen gates green. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Generated-with: claude-opus-4.8 Copilot-Session: cd48fff2-bde9-487a-ab67-012e9bbc0796
Gudge (MGudgin)
force-pushed
the
user/gudge/trust-model-profileoverride-devonly
branch
from
August 8, 2026 21:05
8d97f3f to
d3ac5f7
Compare
Contributor
There was a problem hiding this comment.
Pull request overview
Restricts Seatbelt profile overrides to debug builds, preventing shipped binaries from honoring caller-supplied profiles.
Changes:
- Rejects overrides during release configuration parsing.
- Compiles override handling out of release profile generation.
- Updates tests, schemas, generated types, and documentation.
Show a summary per file
| File | Description |
|---|---|
src/core/wxc_common/src/wire.rs |
Documents the release restriction. |
src/core/wxc_common/src/config_parser.rs |
Rejects overrides in release builds. |
src/backends/seatbelt/common/src/profile_builder.rs |
Disables override generation in releases. |
sdk/node/src/generated/wire.ts |
Updates generated field documentation. |
schemas/dev/mxc-config.schema.0.8.0-dev.json |
Updates generated schema description. |
docs/schema.md |
Documents the configuration restriction. |
docs/macos-support/seatbelt-backend.md |
Documents dev-only override behavior. |
Review details
Tip
Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
- Files reviewed: 6/7 changed files
- Comments generated: 4
- Review effort level: Balanced
| /// If `request.seatbelt.profile_override` is set, that string is returned | ||
| /// verbatim and policy fields are ignored. This whole-profile escape hatch is | ||
| /// **dev-only**: the branch is compiled out of release builds (and the config | ||
| /// parser strips `profileOverride` in release anyway), so a shipped binary |
Comment on lines
+655
to
+658
| #[cfg(not(debug_assertions))] | ||
| let profile_override: Option<String> = { | ||
| if profile_override.is_some() { | ||
| let msg = "seatbelt.profileOverride is a dev-only capability and is \ |
| }, | ||
| "seatbelt": { // macOS sandbox settings (macOS only) | ||
| "profileOverride": null, // Optional raw TinyScheme profile (escape hatch) | ||
| "profileOverride": null, // Dev-only escape hatch (stripped in release builds) |
| | Field | Type | Default | Description | | ||
| |---|---|---|---| | ||
| | `seatbelt.profileOverride` | string | unset | Optional override of the generated TinyScheme sandbox profile. When set, the SDK-generated profile is replaced with this raw TinyScheme string verbatim — all `filesystem`/`network`/`ui` policy fields are ignored for profile generation (they are still type-checked). Use this only when the auto-generated profile is insufficient. | | ||
| | `seatbelt.profileOverride` | string | unset | **Dev-only.** Optional override of the generated TinyScheme sandbox profile. When set, the generated profile is replaced with this raw TinyScheme string verbatim. Because it bypasses the deny-default profile entirely, it is a catastrophic escape hatch: **release/shipped builds strip it at parse time (logging a `SECURITY` line) and compile the override path out**, so it is honored only in dev/debug builds. | |
This PR fixes the release-facing documentation and integration coverage for the dev-only Seatbelt profile override. Details * Describe release behavior consistently as rejecting profileOverride rather than silently stripping it. * Document the restriction on the public TypeScript SeatbeltConfig type. * Make the packaged macOS integration suite assert release rejection instead of expecting the debug-only override path. Tests * npm run build (sdk/node) * npm run build (sdk/node/tests/integration) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e9ded049-d001-46d4-b6c2-9351f4ce1468
Contributor
There was a problem hiding this comment.
Review details
Suppressed comments (4)
src/backends/seatbelt/common/src/profile_builder.rs:41
- The parser does not strip this field in release builds; it rejects the entire request. Saying “strips” contradicts both
make_seatbelt_configand the fail-closed behavior documented immediately above.
/// parser strips `profileOverride` in release anyway), so a shipped binary
sdk/node/tests/integration/macos-seatbelt.test.ts:251
- This integration test is not actually restricted to release binaries.
findSeatbeltExecutable()can select the SDK-bundled or Cargo debug executable, andbuild-mac.sh --debugcopies that debug binary into the preferred SDK path. In that supported debug workflow the override is intentionally honored, so this assertion fails. Gate the test on a known release artifact (or make the expected result profile-aware) and retain a debug integration assertion for the intended pass-through behavior.
const result = await spawnFromConfigAsync(config, seatbeltSpawnOptions);
assert.notStrictEqual(result.exitCode, 0, 'release builds must reject profileOverride');
docs/schema.md:97
- This updated example still nests
seatbeltinsideexperimental, but the parser accepts Seatbelt configuration only at the top level and explicitly rejectsexperimental.seatbelt(config_parser.rs:1574-1579). A debug-build user following this example therefore cannot use the dev-only override at all. Move the wholeseatbeltblock alongsidelxcrather than leaving it underexperimental.
"profileOverride": null, // Dev-only escape hatch (rejected by release builds)
schemas/dev/mxc-config.schema.0.8.0-dev.json:809
- The PR description says
schema.mdwill document that released schemas still advertise this field as generally usable, but this change only updates the dev-schema description. The immutable 0.7 stable schema still calls it an optional override even though release binaries now reject it, so users validating against that schema need the promised compatibility note indocs/schema.md.
"description": "Replace the generated profile entirely (dev-only escape hatch; rejected by release builds).",
- Files reviewed: 8/9 changed files
- Comments generated: 0 new
- Review effort level: Balanced
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR removes the catastrophic seatbelt.profileOverride escape hatch from
release binaries. profileOverride replaces the entire generated deny-default
Seatbelt profile with a caller-supplied string, so it must not be honorable in a
shipped build. Release builds reject a config that sets it, with defense in depth
at both the parse and build layers.
Details
when profile_override is set, and logs a SECURITY line naming the field.
Rejecting rather than dropping the field means a caller never runs under a
policy they did not request: the generated profile can be materially more
permissive than the custom one they supplied. This matches how the reserved
learning-mode capabilities are handled.
#[cfg(not(debug_assertions))], so a release binary builds the generated
deny-default profile even if it were reached with an override set. The parser
rejection alone would be a single point of failure.
the field as rejected by release builds. schema.md records that the released
stable schemas describe it as generally usable, since those files are immutable
and cannot be corrected in place.
Tests
that the error names the field and states the dev-only rejection, and that a
SECURITY line is logged. profile_override_branch_is_absent_in_release asserts
the builder returns the generated deny-default profile rather than the caller's
string, and that the request policy is reflected in it.
profile_override_takes_precedence assert the dev behavior.
in debug, 555 + 36 in release, 0 failed.
-D warnings both clean; codegen gates green.
🔗 References
Stack, merge bottom-up. This PR targets
user/gudge/trust-model-relaxation-logging, so the diff shown is only this change; review #726 first.seatbelt.profileOverridein shipped builds